<?php

    #-------------------------------------------------------------#
	# This file is part of the Tekuna MVC Microframework for PHP. #
	# (c) 2009 Niels Jäckel <niels.jaeckel@tekuna.org>            #
	# Please visit http://tekuna.org/ for more information.       #
	#                                                             #
	# This software is licensed under the MIT License. Please     #
	# view the LICENSE file for the full text of this license.    #
    #-------------------------------------------------------------#


	Tekuna :: load('org.tekuna.core.response.ResponseException');


	/**
	 * This class abstracts the response that is sent to the client. It contains
	 * the content, headers, the current status and the content type. A switch can
	 * be altered to send all debug outputs (outputs that are not registered with
	 * the Response object) or to supress them. The default value is to supress all
	 * outputs, that are not registered in the Response object.
	 */

	class Response {

		protected
			$sProtocol = 'HTTP/1.1',
			$iStatus = 200,
			$sContentType = 'text/html',
			$arrHeaders = array(),
			$sContent = '',
			$bSendDebugOutputs = FALSE,

			$arrStati = array(100 => 'Continue',
			                  101 => 'Switching Protocols',
			                  200 => 'OK',
			                  201 => 'Created',
			                  202 => 'Accepted',
			                  203 => 'Non-Authoritative Information',
			                  204 => 'No Content',
			                  205 => 'Reset Content',
			                  206 => 'Partial Content',
			                  300 => 'Multiple Choices',
			                  301 => 'Moved Permanently',
			                  302 => 'Moved Temporarily',
			                  303 => 'See Other',
			                  304 => 'Not Modified',
			                  304 => 'Use Proxy',
			                  307 => 'Temporary Redirect',
			                  400 => 'Bad Request',
			                  401 => 'Unauthorized',
			                  402 => 'Payment Required',
			                  403 => 'Forbidden',
			                  404 => 'Not Found',
			                  405 => 'Method Not Allowed',
			                  406 => 'Not Acceptable',
			                  407 => 'Proxy Authentication Required',
			                  408 => 'Request Timeout',
			                  409 => 'Conflict',
			                  410 => 'Gone',
			                  411 => 'Length Required',
			                  412 => 'Precondition Failed',
			                  413 => 'Request Entity Too Large',
			                  414 => 'Request-URL Too Long',
			                  415 => 'Unsupported Media Type',
			                  416 => 'Requested Range Not Satisfiable',
			                  417 => 'Expectation Failed',
			                  500 => 'Internal Server Error',
			                  501 => 'Not Implemented',
			                  502 => 'Bad Gateway',
			                  503 => 'Service Unavailable',
			                  504 => 'Gateway Timeout',
			                  505 => 'HTTP Version Not Supported');


		/**
		 * Response's constructor. Output buffering is enabled here to
		 * catch all intermediate debug messages.
		 */

		public function __construct() {

			// start output buffering to collect debug outputs
			ob_start();
		}


		/**
		 * Set the response protocol (e.g. HTTP/1.1)
		 *
		 * @param string $sProtocol
		 */

		public function setResponseProtocol($sProtocol) {

			$this -> sProtocol = $sProtocol;
		}


		/**
		 * @return string return the response protocol (e.g. HTTP/1.1).
		 */

		public function getResponseProtocol() {

			return $this -> sProtocol;
		}


		/**
		 * Set the status code of the response. The default value
		 * is 200 (everything is OK).
		 *
		 * Possible status codes are defined in RFC 2616, section 10
		 *
		 * @param integer $iStatus the status code
		 */

		public function setStatus($iStatus) {

			// check existence of the status
			if (! isset($this -> arrStati[$iStatus])) {

				throw new ResponseException("The status '$iStatus' is not implemented.");
			}

			$this -> iStatus = $iStatus;
		}


		/**
		 * @return integer returns the current status code
		 */

		public function getStatus() {

			return $this -> iStatus;
		}


		/**
		 * Set the content type of the reponse. The default value is text/html.
		 *
		 * @param string $sContentType the returned content type
		 */

		public function setContentType($sContentType) {

			// check the format of the content type
			if (strpos($sContentType, '/') === FALSE) {

				throw new ResponseException("The content type '$sContentType' is not valid.");
			}

			$this -> sContentType = $sContentType;
		}


		/**
		 * @return string returns the content type of the response
		 */

		public function getContentType() {

			return $this -> sContentType;
		}


		/**
		 * Set a specific header field
		 *
		 * @param string $sProperty the header name
		 * @param string $sValue the header value
		 */

		public function setHeader($sProperty, $sValue) {

			$this -> arrHeaders[$sProperty] = $sValue;
		}


		/**
		 * Checks if a specific header is still set
		 *
		 * @param string $sProperty
		 * @return boolean returns TRUE if the requested header exists
		 */

		public function headerExists($sProperty) {

			return isset($this -> arrHeaders[$sProperty]);
		}


		/**
		 * Returns the value of a specific header. If the header does not
		 * exists, a ResponseException is thrown.
		 *
		 * @param string $sProperty the header name
		 * @return string the header value
		 */

		public function getHeader($sProperty) {

			if ($this -> headerExists($sProperty)) {

				return $this -> arrHeaders[$sProperty];
			}
			else {

				throw new ResponseException("No header for property '$sProperty' available.");
			}
		}


		/**
		 * Add new content to the content buffer. All productive contents
		 * must be added with this method to the output that will be sent to
		 * the client.
		 *
		 * @param string $sContent the new content part
		 */

		public function appendContent($sContent) {

			$this -> sContent .= $sContent;
		}


		/**
		 * @return string returns the current content buffer
		 */

		public function getContent() {

			return $this -> sContent;
		}


		/**
		 * Resets the content buffer (Set it to the empty string).
		 */

		public function resetContentBuffer() {

			$this -> sContent = '';
		}


		/**
		 * This method defines if outputs, that are not registere in the Response
		 * object with the method appendContent() should be sent to the client or
		 * not. The default value is FALSE.
		 *
		 * For debugging purposes it could be necessary to send all intermediate
		 * echo() or print_r() outputs to the client
		 *
		 * @param boolean $bSend set TRUE to send everything, FALSE to send only registered contents.
		 */

		public function setSendDebugOutputs($bSend) {

			$this -> bSendDebugOutputs = $bSend;
		}


		/**
		 * Send the HTTP response to the client. All headers (including the
		 * status header with the correct protocol and version) and the content
		 * are sent with this.
		 *
		 * The output buffer, that was started within the constructor is ended here
		 * and if debug output submission is enabled (this is not default), the
		 * debug outputs are prepended to the real page content.
		 */

		public function sendResponse() {

			// if sending debug outputs is enabled prepend it to the content
			if ($this -> bSendDebugOutputs) {

				$sDebugOutputs = ob_get_contents();
				$this -> sContent = $sDebugOutputs . $this -> sContent;
			}

			// end debug collecting output buffer
			ob_end_clean();

			// send all headers
			$this -> sendHeaders();

			// start compression output handler
			if (extension_loaded('zlib')) {

				ob_start("ob_gzhandler");
			}
			else {

				ob_start();
			}

			// put the buffered content to the php output buffer
			echo $this -> sContent;

			// send the buffer
			ob_end_flush();
		}


		protected function sendHeaders() {

			// send status header
			$this -> sendStatusHeader($this -> iStatus);

			// send content type header
			header("Content-Type: $this->sContentType; charset=utf-8");

			// send Tekuna 'marker'
			header('X-Powered-By: Tekuna MVC Microframework for PHP version '. TEKUNA_VERSION);

			// send other headers
			foreach ($this -> arrHeaders as $sProperty => $sValue) {

				header("$sProperty: $sValue");
			}
		}


		protected function sendStatusHeader($iStatus) {

			// default protocol line
			$sProtocol = $this -> getResponseProtocol();

			// get message for status
			$sMessage = $this -> arrStati[$iStatus];

			// send the status header
			header("$sProtocol $iStatus $sMessage");
		}
	}
